#include <algorithm>
#include <functional>
#include <iostream>
#include <iterator>

using namespace std;

namespace YTQ {

// 1-----hash_val
template <typename T> void hash_combine(size_t& seed, const T& val) {
    seed ^= std::hash<T>()(val) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
template <class T> inline void hash_val(size_t& seed, const T& val) {
    cout << "val0:" << endl;

    hash_combine(seed, val);
}
template <class T, typename... Types>
inline void hash_val(size_t& seed, const T& val, const Types&... args) {
    cout << "val:" << sizeof...(args) << endl;

    hash_combine(seed, val);
    hash_val(seed, args...);
}
template <typename... Types> 
inline size_t hash_val(const Types&... args) {
    cout << "val1:" << sizeof...(args) << endl;
    size_t seed = 0;
    hash_val(seed, args...);
    return seed;
}

// 2----customer
class Customer {
  public:
    Customer(std::string name, std::string name1, unsigned long age)
        : fname(name), lname(name1), no(age) {}
    bool operator==(const Customer& obj) const {
        return (this->fname == obj.fname) && (this->lname == obj.lname) &&
               (this->no == obj.no);
    }

  public:
    std::string fname, lname;
    unsigned long no;
};

struct CustomerHash {
    size_t operator()(const Customer& obj) const {
        return hash_val(obj.fname, obj.lname, obj.no);
    }
};
size_t customer_hash_func(const Customer& obj) {
    return hash_val(obj.fname, obj.lname, obj.no);
}

} // namespace YTQ

namespace std {

template <>
struct hash<YTQ::Customer> : public __hash_base<size_t, YTQ::Customer> {
    size_t operator()(const YTQ::Customer& obj) const noexcept {
        return YTQ::hash_val(obj.fname, obj.lname, obj.no);
    }
};

} // namespace std

#include <unordered_set>

void testCppPeriphery() {
    // 0
    YTQ::CustomerHash hh;

    cout << "bucket position of Ace= \n"<< hh(YTQ::Customer("Ace", "Hou", 1L)) % 13 << endl;

    cout << "bucket position of Ace1= "
         << hh(YTQ::Customer("Sabri", "Hou", 2L)) % 13 << endl;
    cout << "bucket position of Ace2= "
         << hh(YTQ::Customer("Ace1", "Chen", 3L)) % 13 << endl;
    cout << "bucket position of Ace3= "
         << hh(YTQ::Customer("Ace2", "Tseng", 4L)) % 13 << endl;
    cout << "bucket position of Ace4= "
         << hh(YTQ::Customer("Ace3", "Chen", 5L)) % 13 << endl;
    cout << "bucket position of Ace5= "
         << hh(YTQ::Customer("Ace4", "Shiau", 6L)) % 13 << endl;
    cout << "bucket position of Ace6= "
         << hh(YTQ::Customer("Shally", "Hwung", 7L)) % 13 << endl;

    // 1---customer_hashobj
    unordered_set<YTQ::Customer, YTQ::CustomerHash> s1;
    s1.insert(YTQ::Customer("Ace", "Hou", 1L));
    s1.insert(YTQ::Customer("Sabri", "Hou", 2L));
    s1.insert(YTQ::Customer("Ace1", "Chen", 3L));
    s1.insert(YTQ::Customer("Ace2", "Tseng", 4L));
    s1.insert(YTQ::Customer("Ace3", "Chen", 5L));
    s1.insert(YTQ::Customer("Ace4", "Shiau", 6L));
    s1.insert(YTQ::Customer("Shally", "Hwung", 7L));
    for (int i = 0; i != s1.bucket_count(); ++i) {
        cout << "bucket#" << i << " has " << s1.bucket_size(i) << " elements"
             << endl;
    }

    // 2---customer_hashFunc
    unordered_set<YTQ::Customer, size_t (*)(const YTQ::Customer& obj)> s2(
        13, YTQ::customer_hash_func);

    s2.insert(YTQ::Customer("Ace", "Hou", 1L));
    s2.insert(YTQ::Customer("Sabri", "Hou", 2L));
    s2.insert(YTQ::Customer("Ace1", "Chen", 3L));
    s2.insert(YTQ::Customer("Ace2", "Tseng", 4L));
    s2.insert(YTQ::Customer("Ace3", "Chen", 5L));
    s2.insert(YTQ::Customer("Ace4", "Shiau", 6L));
    s2.insert(YTQ::Customer("Shally", "Hwung", 7L));
    for (int i = 0; i != s2.bucket_count(); ++i) {
        cout << "s2 bucket#" << i << " has " << s2.bucket_size(i) << " elements"
             << endl;
    }
}